package tk.winshu.shortestpath.view;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.winshu.shortestpath.algorithm.Dijkstra;
import tk.winshu.shortestpath.model.*;
import tk.winshu.shortestpath.operate.*;
import tk.winshu.shortestpath.util.ArrowDrawUtil;
import tk.winshu.shortestpath.util.MoveUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Jason Krebs
 * @date 2015年2月2日
 */
public class CanvasPanel extends JPanel {

    public interface IRunningFinishedListener {
        /**
         * 回调接口
         */
        void onRunningFinished();
    }

    static final Image NODE_IMAGE = Toolkit.getDefaultToolkit().getImage(CanvasPanel.class.getResource("/image/node_default.png"));
    private static final Image RUNNER = Toolkit.getDefaultToolkit().getImage(CanvasPanel.class.getResource("/image/Runner.png"));
    private static final Logger log = LoggerFactory.getLogger(CanvasPanel.class);

    private NodeManager nodeManager;

    /**
     * 背景图
     */
    private Image rawImage;

    /**
     * 运行者
     */
    private Runner runner;

    /**
     * 临时点（绘制时使用）
     */
//    private Point2D tempPoint;

    /**
     * 临时节点
     */
    private Node tempNode;

    /**
     * 是否有向图
     */
    private boolean isDirectedGraph;
    /**
     * 是否显示权重
     */
    private boolean isWeightVisible;
    /**
     * 是否循环运行
     */
    private boolean keepLoopRunning;
    /**
     * 是否正在运行
     */
    private boolean isRunning;

    /**
     * 路径
     */
    private List<Integer> paths;

    /**
     * 回调监听器
     */
    private IRunningFinishedListener runningFinishedListener;

    private MouseListener mouseAdapter = new MouseAdapter() {
        @Override
        public void mouseReleased(MouseEvent e) {
            if (e.getButton() == MouseEvent.BUTTON1) {
                if (tempNode == null || getFocusedNode() == null) {
                    return;
                }
                // 获取临时点所在节点，若不为空，则可以创建一条连线关系
                Node realNode = getNode(tempNode.getLocation());
                if (realNode != null && getFocusedNode() != realNode) {
                    nodeManager.execute(new AddLine(getFocusedNode(), realNode));
                }
            }
            // 重置临时点
            tempNode.setVisible(false);
        }
    };

    private MouseMotionListener mouseMotionAdapter = new MouseMotionAdapter() {
        @Override
        public void mouseDragged(MouseEvent e) {
            createTempLine(e.getPoint());
        }

        // 鼠标移动时，更新焦点节点
        @Override
        public void mouseMoved(MouseEvent e) {
            if (nodeManager.setFocusedNode(getNode(e.getPoint()))) {
                repaint();
            }
        }
    };

    CanvasPanel() {
        tempNode = new Node();
        tempNode.setVisible(false);

        nodeManager = new NodeManager();
        // 监听鼠标释放动作，用于绘制节点和连线
        addMouseListener(mouseAdapter);
        // 监听鼠标轨迹动作，用于绘制直线和更新焦点节点
        addMouseMotionListener(mouseMotionAdapter);
    }

    /**
     * 清空画板
     */
    void cleanAll() {
        nodeManager.clear();
        tempNode.setVisible(false);
        runner = null;
        repaint();
    }

    /**
     * 清空所有连线
     */
    void cleanLines() {
        nodeManager.execute(new CleanAllLines());
        repaint();
    }

    /**
     * 绘制连线
     */
    private void drawAllLine(Graphics2D g2) {
        for (Node node : nodeManager.getNodes()) {
            for (Node endNode : node.getEndNodes()) {
                // 运行中的路径显示红色
                g2.setColor(isRunning && isLineInPath(node.getIndex(), endNode.getIndex()) ? Color.RED : Color.GREEN);

                Point2D startPoint = node.getLocation();
                Point2D endPoint = endNode.getLocation();
                drawLine(g2, startPoint, endPoint);
                // 绘制权重
                if (isWeightVisible) {
                    g2.drawString(String.format("%.0f",
                            node.distanceTo(endNode)),
                            (float) (startPoint.getX() + endPoint.getX()) / 2,
                            (float) (startPoint.getY() + endPoint.getY()) / 2);
                }
            }
        }
        // 绘制临时点到焦点的直线
        if (tempNode.isVisible() && getFocusedNode() != null) {
            Node node = getNode(tempNode.getLocation());
            g2.setColor(node == null ? NodeStatus.TEMP.getColor() : Color.YELLOW);
            drawLine(g2, getFocusedNode().getLocation(), tempNode.getLocation());
        }
    }

    private void drawLine(Graphics2D g2, Point2D a, Point2D b) {
        if (isDirectedGraph) {
            ArrowDrawUtil.draw(g2, a, b);
        } else {
            g2.draw(new Line2D.Double(a, b));
        }
    }

    private void drawPlayer(Graphics2D g2d) {
        if (runner != null) {
            runner.draw(g2d);
        }
    }

    /**
     * 交换起点和终点
     */
    void exchange() {
        nodeManager.exchange();
        repaint();
    }

    /**
     * 创建临时直线
     */
    private void createTempLine(Point2D point) {
        if (getFocusedNode() == null) {
            return;
        }

        Node realNode = getNode(point);
        tempNode.setLocation(realNode == null ? point : realNode.getLocation());
        tempNode.setVisible(true);

        repaint();
    }

    /**
     * 在指定点创建节点
     */
    void createNode(Point2D point) {
        nodeManager.execute(new AddNode(point));
        repaint();
    }

    NodeData buildNodeData() {
        return nodeManager.buildNodeData();
    }

    Node getFocusedNode() {
        return nodeManager.getFocusedNode();
    }

    /**
     * 根据位置获取节点，如果没找到则返回null
     */
    Node getNode(Point2D point) {
        return nodeManager.getNode(point);
    }

    int getNodeCount() {
        return nodeManager.getNodes().size();
    }

    boolean isRunnable() {
        return !isRunning && nodeManager.isReady();
    }

    boolean isRunning() {
        return isRunning;
    }

    void loadNodes(NodeData nodeData) {
        nodeManager.execute(new ImportNode(nodeData.getNodes()));
        nodeManager.setStartNode(nodeData.getStartNode());
        nodeManager.setEndNode(nodeData.getEndNode());
        repaint();
    }

    /**
     * 移动节点
     */
    void moveNode(Point2D point) {
        Node focusedNode = getFocusedNode();
        if (focusedNode != null) {
            // TODO 移动节点的功能需要完善了才能用
//            nodeManager.execute(new MoveNode(point, focusedNode, runner));
            focusedNode.setLocation(point);
            // runner 在当前节点上，一并移动
            if (runner != null && runner.getIndex() == focusedNode.getIndex()) {
                runner.setLocation(point);
            }
            repaint();
        }
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        // 绘制底图
        if (rawImage != null) {
            g.drawImage(rawImage, 0, 0, null);
        }
        nodeManager.drawNode((Graphics2D) g);
        drawAllLine((Graphics2D) g);
        drawPlayer((Graphics2D) g);
    }

    /**
     * 移除焦点节点的连线
     */
    boolean removeLine() {
        Node focusedNode = getFocusedNode();
        if (focusedNode == null) {
            return false;
        }
        nodeManager.execute(new CleanLines(focusedNode));
        repaint();
        return true;
    }

    /**
     * 移除焦点节点
     */
    boolean removeNode() {
        Node focusedNode = getFocusedNode();
        if (focusedNode == null) {
            return false;
        }
        nodeManager.execute(new RemoveNode(focusedNode));
        repaint();
        return true;
    }

    /**
     * 重置运行者
     */
    private void resetRunner() {
        if (nodeManager.isEmpty()) {
            return;
        }
        if (runner == null) {
            Node node = nodeManager.getFirstNode();
            runner = new Runner(node.getIndex(), node.getLocation(), RUNNER);
        }
        Node startNode = nodeManager.getStartNode();
        if (startNode != null) {
            runner.setLocation(startNode.getLocation());
            runner.setIndex(startNode.getIndex());
        }
    }

    /**
     * 运行移动动画
     */
    void runAction() {
        isRunning = true;
        // 停止监听
        removeMouseListener(mouseAdapter);
        removeMouseMotionListener(mouseMotionAdapter);

        Dijkstra dijkstra = new Dijkstra(isDirectedGraph);
        List<Node> nodePaths = dijkstra.find(nodeManager.getStartNode(), nodeManager.getEndNode());
        if (nodePaths.isEmpty()) {
            log.warn("找不到路径");
            stopAction();
            return;
        }

        // 标记节点
        this.paths = nodePaths.stream().map(NObject::getIndex).collect(Collectors.toList());
        Thread thread = new Thread(() -> {
            do {
                resetRunner();
                for (Node node : nodePaths) {
                    // 中断操作
                    if (!isRunning) {
                        break;
                    }
                    runToNode(node);
                }
            } while (isRunning && keepLoopRunning);
            // 停止运行
            stopAction();
        });
        thread.setDaemon(true);
        thread.start();
    }

    private void runToNode(Node nextNode) {
        runner.setIndex(nextNode.getIndex());
        Point2D source = runner.getLocation();
        Point2D target = nextNode.getLocation();

        while (!nextNode.getLocation().equals(runner.getLocation())) {
            Point2D nextLocation = MoveUtil.nextLocation(runner.getLocation(), source, target);
            runner.setLocation(nextLocation);
            repaint();
            try {
                Thread.sleep(17);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * 是否有向图
     */
    void setDirectedGraph(boolean isDirectedGraph) {
        this.isDirectedGraph = isDirectedGraph;
        repaint();
    }

    /**
     * 是否循环运动
     */
    void setKeepLoopRunning(boolean isLoopRunning) {
        this.keepLoopRunning = isLoopRunning;
    }

    /**
     * 标记焦点为终点
     */
    void markFocusedAsEndNode() {
        if (nodeManager.markFocusedAsEndNode()) {
            repaint();
        }
    }

    /**
     * 设置焦点为开始节点
     */
    void markFocusedAsStartNode() {
        if (nodeManager.markFocusedAsStartNode()) {
            repaint();
        }
    }

    /**
     * 是否显示权重
     */
    void setWeightVisible(boolean weightVisible) {
        this.isWeightVisible = weightVisible;
        repaint();
    }

    /**
     * 注册运动事件
     */
    void registerRunningEvent(IRunningFinishedListener runningFinishedListener) {
        this.runningFinishedListener = runningFinishedListener;
    }

    /**
     * 停止正在运行的动作
     */
    void stopAction() {
        if (!isRunning) {
            return;
        }
        isRunning = false;
        if (runningFinishedListener != null) {
            runningFinishedListener.onRunningFinished();
        }
        addMouseListener(mouseAdapter);
        addMouseMotionListener(mouseMotionAdapter);
        // 重置标记状态
        this.paths = null;
    }

    void setRawImage(Image rawImage) {
        if (rawImage != null) {
            this.rawImage = rawImage;
            setBounds(0, 0, rawImage.getWidth(this), rawImage.getHeight(this));
            setPreferredSize(new Dimension(rawImage.getWidth(this), rawImage.getHeight(this)));
            repaint();
        }
    }

    void historyToPrevious() {
        if (nodeManager.historyToPrevious()) {
            repaint();
        }
    }


    void historyToNext() {
        if (nodeManager.historyToNext()) {
            repaint();
        }
    }

    /**
     * 判断一条直线是否在路中
     */
    private boolean isLineInPath(int startIndex, int endIndex) {
        if (paths == null) {
            return false;
        }
        int pathIndex = paths.indexOf(startIndex);
        if (pathIndex < 0 || pathIndex == paths.size() - 1) {
            return false;
        }
        return endIndex == paths.get(pathIndex + 1);
    }
}
